using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using LiteNetLib.Utils;

namespace LiteNetLib
{
	public sealed class NetPeer
	{
		private class IncomingFragments
		{
			public NetPacket[] Fragments;

			public int ReceivedCount;

			public int TotalSize;
		}

		private int _rtt;

		private int _avgRtt;

		private int _rttCount;

		private double _resendDelay = 27.0;

		private int _pingSendTimer;

		private int _rttResetTimer;

		private readonly Stopwatch _pingTimer = new Stopwatch();

		private int _timeSinceLastPacket;

		private long _remoteDelta;

		private readonly IPEndPoint _remoteEndPoint;

		private readonly NetManager _netManager;

		private readonly NetPacketPool _packetPool;

		private readonly object _flushLock = new object();

		private readonly object _sendLock = new object();

		internal NetPeer NextPeer;

		internal NetPeer PrevPeer;

		private readonly ReliableChannel _reliableOrderedChannel;

		private readonly ReliableChannel _reliableUnorderedChannel;

		private readonly SequencedChannel _sequencedChannel;

		private readonly SimpleChannel _unreliableChannel;

		private readonly SequencedChannel _reliableSequencedChannel;

		private int _mtu = NetConstants.PossibleMtu[0];

		private int _mtuIdx;

		private bool _finishMtu;

		private int _mtuCheckTimer;

		private int _mtuCheckAttempts;

		private const int MtuCheckDelay = 1000;

		private const int MaxMtuCheckAttempts = 4;

		private readonly object _mtuMutex = new object();

		private ushort _fragmentId;

		private readonly Dictionary<ushort, IncomingFragments> _holdedFragments;

		private readonly NetPacket _mergeData;

		private int _mergePos;

		private int _mergeCount;

		private int _connectAttempts;

		private int _connectTimer;

		private long _connectTime;

		private byte _connectNum;

		private ConnectionState _connectionState;

		private NetPacket _shutdownPacket;

		private const int ShutdownDelay = 300;

		private int _shutdownTimer;

		private readonly NetPacket _pingPacket;

		private readonly NetPacket _pongPacket;

		private readonly NetPacket _connectRequestPacket;

		private NetPacket _connectAcceptPacket;

		public readonly int Id;

		public object Tag;

		public readonly NetStatistics Statistics;

		internal byte ConnectionNum
		{
			get
			{
				return _connectNum;
			}
			private set
			{
				_connectNum = value;
				_mergeData.ConnectionNumber = value;
				_pingPacket.ConnectionNumber = value;
				_pongPacket.ConnectionNumber = value;
			}
		}

		public ConnectionState ConnectionState
		{
			get
			{
				return _connectionState;
			}
		}

		internal long ConnectTime
		{
			get
			{
				return _connectTime;
			}
		}

		public IPEndPoint EndPoint
		{
			get
			{
				return _remoteEndPoint;
			}
		}

		public int Ping
		{
			get
			{
				return _avgRtt / 2;
			}
		}

		public int Mtu
		{
			get
			{
				return _mtu;
			}
		}

		public long RemoteTimeDelta
		{
			get
			{
				return _remoteDelta;
			}
		}

		public DateTime RemoteUtcTime
		{
			get
			{
				return new DateTime(DateTime.UtcNow.Ticks + _remoteDelta);
			}
		}

		public int TimeSinceLastPacket
		{
			get
			{
				return _timeSinceLastPacket;
			}
		}

		public NetManager NetManager
		{
			get
			{
				return _netManager;
			}
		}

		public int PacketsCountInReliableQueue
		{
			get
			{
				return _reliableUnorderedChannel.PacketsInQueue;
			}
		}

		public int PacketsCountInReliableOrderedQueue
		{
			get
			{
				return _reliableOrderedChannel.PacketsInQueue;
			}
		}

		internal double ResendDelay
		{
			get
			{
				return _resendDelay;
			}
		}

		internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id)
		{
			Id = id;
			Statistics = new NetStatistics();
			_packetPool = netManager.NetPacketPool;
			_netManager = netManager;
			_remoteEndPoint = remoteEndPoint;
			_connectionState = ConnectionState.Incoming;
			_mergeData = new NetPacket(PacketProperty.Merged, NetConstants.MaxPacketSize);
			_pongPacket = new NetPacket(PacketProperty.Pong, 0);
			_pingPacket = new NetPacket(PacketProperty.Ping, 0);
			_pingPacket.Sequence = 1;
			_reliableOrderedChannel = new ReliableChannel(this, true);
			_reliableUnorderedChannel = new ReliableChannel(this, false);
			_sequencedChannel = new SequencedChannel(this, false);
			_unreliableChannel = new SimpleChannel(this);
			_reliableSequencedChannel = new SequencedChannel(this, true);
			_holdedFragments = new Dictionary<ushort, IncomingFragments>();
		}

		internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id, byte connectNum, NetDataWriter connectData)
			: this(netManager, remoteEndPoint, id)
		{
			_connectTime = DateTime.UtcNow.Ticks;
			_connectionState = ConnectionState.Outcoming;
			ConnectionNum = connectNum;
			_connectRequestPacket = NetConnectRequestPacket.Make(connectData, _connectTime);
			_connectRequestPacket.ConnectionNumber = connectNum;
			_netManager.SendRaw(_connectRequestPacket, _remoteEndPoint);
		}

		internal void Accept(long connectId, byte connectNum)
		{
			_connectTime = connectId;
			_connectionState = ConnectionState.Connected;
			ConnectionNum = connectNum;
			_connectAcceptPacket = NetConnectAcceptPacket.Make(_connectTime, connectNum, false);
			_netManager.SendRaw(_connectAcceptPacket, _remoteEndPoint);
		}

		internal bool ProcessConnectAccept(NetConnectAcceptPacket packet)
		{
			if (_connectionState != ConnectionState.Outcoming)
			{
				return false;
			}
			if (packet.ConnectionId != _connectTime)
			{
				return false;
			}
			ConnectionNum = packet.ConnectionNumber;
			_timeSinceLastPacket = 0;
			_connectionState = ConnectionState.Connected;
			return true;
		}

		private static PacketProperty SendOptionsToProperty(DeliveryMethod options)
		{
			switch (options)
			{
			case DeliveryMethod.ReliableUnordered:
				return PacketProperty.ReliableUnordered;
			case DeliveryMethod.Sequenced:
				return PacketProperty.Sequenced;
			case DeliveryMethod.ReliableOrdered:
				return PacketProperty.ReliableOrdered;
			case DeliveryMethod.ReliableSequenced:
				return PacketProperty.ReliableSequenced;
			default:
				return PacketProperty.Unreliable;
			}
		}

		public int GetMaxSinglePacketSize(DeliveryMethod options)
		{
			return _mtu - NetPacket.GetHeaderSize(SendOptionsToProperty(options));
		}

		public void Send(byte[] data, DeliveryMethod options)
		{
			Send(data, 0, data.Length, options);
		}

		public void Send(NetDataWriter dataWriter, DeliveryMethod options)
		{
			Send(dataWriter.Data, 0, dataWriter.Length, options);
		}

		public void Send(byte[] data, int start, int length, DeliveryMethod options)
		{
			if (_connectionState == ConnectionState.ShutdownRequested || _connectionState == ConnectionState.Disconnected)
			{
				return;
			}
			PacketProperty packetProperty = SendOptionsToProperty(options);
			BaseChannel baseChannel;
			switch (packetProperty)
			{
			case PacketProperty.ReliableUnordered:
				baseChannel = _reliableUnorderedChannel;
				break;
			case PacketProperty.Sequenced:
				baseChannel = _sequencedChannel;
				break;
			case PacketProperty.ReliableOrdered:
				baseChannel = _reliableOrderedChannel;
				break;
			case PacketProperty.Unreliable:
				baseChannel = _unreliableChannel;
				break;
			case PacketProperty.ReliableSequenced:
				baseChannel = _reliableSequencedChannel;
				break;
			default:
				throw new InvalidPacketException("Unknown packet property: " + packetProperty);
			}
			int headerSize = NetPacket.GetHeaderSize(packetProperty);
			int mtu = _mtu;
			if (length + headerSize > mtu)
			{
				if (options == DeliveryMethod.Sequenced || options == DeliveryMethod.Unreliable || options == DeliveryMethod.ReliableSequenced)
				{
					throw new TooBigPacketException("Unreliable packet size exceeded maximum of " + (_mtu - headerSize) + " bytes");
				}
				int num = mtu - headerSize;
				int num2 = num - 6;
				int num3 = length / num2;
				int num4 = length % num2;
				int num5 = num3 + ((num4 != 0) ? 1 : 0);
				if (num5 > 65535)
				{
					throw new TooBigPacketException("Data was split in " + num5 + " fragments, which exceeds " + ushort.MaxValue);
				}
				int dstOffset = headerSize + 6;
				lock (_sendLock)
				{
					for (ushort num6 = 0; num6 < num3; num6++)
					{
						NetPacket withProperty = _packetPool.GetWithProperty(packetProperty, num);
						withProperty.FragmentId = _fragmentId;
						withProperty.FragmentPart = num6;
						withProperty.FragmentsTotal = (ushort)num5;
						withProperty.MarkFragmented();
						Buffer.BlockCopy(data, num6 * num2, withProperty.RawData, dstOffset, num2);
						baseChannel.AddToQueue(withProperty);
					}
					if (num4 > 0)
					{
						NetPacket withProperty2 = _packetPool.GetWithProperty(packetProperty, num4 + 6);
						withProperty2.FragmentId = _fragmentId;
						withProperty2.FragmentPart = (ushort)num3;
						withProperty2.FragmentsTotal = (ushort)num5;
						withProperty2.MarkFragmented();
						Buffer.BlockCopy(data, num3 * num2, withProperty2.RawData, dstOffset, num4);
						baseChannel.AddToQueue(withProperty2);
					}
					_fragmentId++;
					return;
				}
			}
			NetPacket withData = _packetPool.GetWithData(packetProperty, data, start, length);
			baseChannel.AddToQueue(withData);
		}

		public void Disconnect(byte[] data)
		{
			_netManager.DisconnectPeer(this, data);
		}

		public void Disconnect(NetDataWriter writer)
		{
			_netManager.DisconnectPeer(this, writer);
		}

		public void Disconnect(byte[] data, int start, int count)
		{
			_netManager.DisconnectPeer(this, data, start, count);
		}

		public void Disconnect()
		{
			_netManager.DisconnectPeer(this);
		}

		internal DisconnectResult ProcessDisconnect(NetPacket packet)
		{
			if ((_connectionState == ConnectionState.Connected || _connectionState == ConnectionState.Outcoming) && packet.Size >= 9 && BitConverter.ToInt64(packet.RawData, 1) == _connectTime && packet.ConnectionNumber == _connectNum)
			{
				if (_connectionState != ConnectionState.Connected)
				{
					return DisconnectResult.Reject;
				}
				return DisconnectResult.Disconnect;
			}
			return DisconnectResult.None;
		}

		internal void Reject(long connectionId, byte connectionNumber, byte[] data, int start, int length)
		{
			_connectTime = connectionId;
			_connectNum = connectionNumber;
			Shutdown(data, start, length, false);
		}

		internal bool Shutdown(byte[] data, int start, int length, bool force)
		{
			lock (this)
			{
				if (_connectionState == ConnectionState.Disconnected || _connectionState == ConnectionState.ShutdownRequested)
				{
					return false;
				}
				if (force)
				{
					_connectionState = ConnectionState.Disconnected;
					return true;
				}
				_timeSinceLastPacket = 0;
				_shutdownPacket = new NetPacket(PacketProperty.Disconnect, length);
				_shutdownPacket.ConnectionNumber = _connectNum;
				FastBitConverter.GetBytes(_shutdownPacket.RawData, 1, _connectTime);
				if (_shutdownPacket.Size >= _mtu)
				{
					NetDebug.WriteError("[Peer] Disconnect additional data size more than MTU - 8!");
				}
				else if (data != null && length > 0)
				{
					Buffer.BlockCopy(data, start, _shutdownPacket.RawData, 9, length);
				}
				_connectionState = ConnectionState.ShutdownRequested;
				_netManager.SendRaw(_shutdownPacket, _remoteEndPoint);
				return true;
			}
		}

		private void UpdateRoundTripTime(int roundTripTime)
		{
			_rtt += roundTripTime;
			_rttCount++;
			_avgRtt = _rtt / _rttCount;
			_resendDelay = 25.0 + (double)_avgRtt * 2.1;
		}

		internal void AddIncomingPacket(NetPacket p)
		{
			if (p.IsFragmented)
			{
				ushort fragmentId = p.FragmentId;
				IncomingFragments value;
				if (!_holdedFragments.TryGetValue(fragmentId, out value))
				{
					IncomingFragments incomingFragments = new IncomingFragments();
					incomingFragments.Fragments = new NetPacket[p.FragmentsTotal];
					value = incomingFragments;
					_holdedFragments.Add(fragmentId, value);
				}
				NetPacket[] fragments = value.Fragments;
				if (p.FragmentPart >= fragments.Length || fragments[p.FragmentPart] != null)
				{
					_packetPool.Recycle(p);
					NetDebug.WriteError("Invalid fragment packet");
					return;
				}
				fragments[p.FragmentPart] = p;
				value.ReceivedCount++;
				int num = p.GetHeaderSize() + 6;
				value.TotalSize += p.Size - num;
				if (value.ReceivedCount == fragments.Length)
				{
					NetPacket withProperty = _packetPool.GetWithProperty(p.Property, value.TotalSize);
					int headerSize = withProperty.GetHeaderSize();
					int num2 = fragments[0].Size - num;
					for (int i = 0; i < value.ReceivedCount; i++)
					{
						int count = fragments[i].Size - num;
						Buffer.BlockCopy(fragments[i].RawData, num, withProperty.RawData, headerSize + num2 * i, count);
						_packetPool.Recycle(fragments[i]);
						fragments[i] = null;
					}
					_netManager.ReceiveFromPeer(withProperty, _remoteEndPoint);
					_holdedFragments.Remove(fragmentId);
				}
			}
			else
			{
				_netManager.ReceiveFromPeer(p, _remoteEndPoint);
			}
		}

		private void ProcessMtuPacket(NetPacket packet)
		{
			if (packet.Size < NetConstants.PossibleMtu[0])
			{
				return;
			}
			int num = BitConverter.ToInt32(packet.RawData, 1);
			int num2 = BitConverter.ToInt32(packet.RawData, packet.Size - 4);
			if (num != packet.Size || num != num2 || num > NetConstants.MaxPacketSize)
			{
				NetDebug.WriteError("[MTU] Broken packet. RMTU {0}, EMTU {1}, PSIZE {2}", num, num2, packet.Size);
			}
			else if (packet.Property == PacketProperty.MtuCheck)
			{
				_mtuCheckAttempts = 0;
				packet.Property = PacketProperty.MtuOk;
				_netManager.SendRawAndRecycle(packet, _remoteEndPoint);
			}
			else if (num > _mtu && !_finishMtu && num == NetConstants.PossibleMtu[_mtuIdx + 1])
			{
				lock (_mtuMutex)
				{
					_mtuIdx++;
					_mtu = num;
				}
				if (_mtuIdx == NetConstants.PossibleMtu.Length - 1)
				{
					_finishMtu = true;
				}
			}
		}

		private void UpdateMtuLogic(int deltaTime)
		{
			if (_finishMtu)
			{
				return;
			}
			_mtuCheckTimer += deltaTime;
			if (_mtuCheckTimer < 1000)
			{
				return;
			}
			_mtuCheckTimer = 0;
			_mtuCheckAttempts++;
			if (_mtuCheckAttempts >= 4)
			{
				_finishMtu = true;
				return;
			}
			lock (_mtuMutex)
			{
				if (_mtuIdx < NetConstants.PossibleMtu.Length - 1)
				{
					int num = NetConstants.PossibleMtu[_mtuIdx + 1];
					NetPacket withProperty = _packetPool.GetWithProperty(PacketProperty.MtuCheck, num - 1);
					FastBitConverter.GetBytes(withProperty.RawData, 1, num);
					FastBitConverter.GetBytes(withProperty.RawData, withProperty.Size - 4, num);
					if (_netManager.SendRawAndRecycle(withProperty, _remoteEndPoint) <= 0)
					{
						_finishMtu = true;
					}
				}
			}
		}

		internal ConnectRequestResult ProcessConnectRequest(NetConnectRequestPacket connRequest)
		{
			switch (_connectionState)
			{
			case ConnectionState.Incoming:
			case ConnectionState.Outcoming:
				if (connRequest.ConnectionTime >= _connectTime)
				{
					_connectTime = connRequest.ConnectionTime;
					ConnectionNum = connRequest.ConnectionNumber;
				}
				if (_connectionState != ConnectionState.Outcoming)
				{
					return ConnectRequestResult.None;
				}
				return ConnectRequestResult.P2PConnection;
			case ConnectionState.Connected:
				if (connRequest.ConnectionTime == _connectTime)
				{
					_netManager.SendRaw(_connectAcceptPacket, _remoteEndPoint);
				}
				else if (connRequest.ConnectionTime > _connectTime)
				{
					return ConnectRequestResult.Reconnection;
				}
				break;
			case ConnectionState.ShutdownRequested:
			case ConnectionState.Disconnected:
				if (connRequest.ConnectionTime >= _connectTime)
				{
					return ConnectRequestResult.NewConnection;
				}
				break;
			}
			return ConnectRequestResult.None;
		}

		internal void ProcessPacket(NetPacket packet)
		{
			if (_connectionState == ConnectionState.Incoming)
			{
				_packetPool.Recycle(packet);
				return;
			}
			if (packet.ConnectionNumber != _connectNum && packet.Property != PacketProperty.ShutdownOk)
			{
				_packetPool.Recycle(packet);
				return;
			}
			_timeSinceLastPacket = 0;
			switch (packet.Property)
			{
			case PacketProperty.Merged:
			{
				int num = 1;
				while (num < packet.Size)
				{
					ushort num2 = BitConverter.ToUInt16(packet.RawData, num);
					num += 2;
					NetPacket packet2 = _packetPool.GetPacket(num2, false);
					if (!packet2.FromBytes(packet.RawData, num, num2))
					{
						_packetPool.Recycle(packet);
						break;
					}
					num += num2;
					ProcessPacket(packet2);
				}
				break;
			}
			case PacketProperty.Ping:
				if (NetUtils.RelativeSequenceNumber(packet.Sequence, _pongPacket.Sequence) > 0)
				{
					FastBitConverter.GetBytes(_pongPacket.RawData, 3, DateTime.UtcNow.Ticks);
					_pongPacket.Sequence = packet.Sequence;
					_netManager.SendRaw(_pongPacket, _remoteEndPoint);
				}
				_packetPool.Recycle(packet);
				break;
			case PacketProperty.Pong:
				if (packet.Sequence == _pingPacket.Sequence)
				{
					_pingTimer.Stop();
					int num3 = (int)_pingTimer.ElapsedMilliseconds;
					_remoteDelta = BitConverter.ToInt64(packet.RawData, 3) + (long)num3 * 10000L / 2 - DateTime.UtcNow.Ticks;
					UpdateRoundTripTime(num3);
					_netManager.ConnectionLatencyUpdated(this, num3 / 2);
				}
				_packetPool.Recycle(packet);
				break;
			case PacketProperty.AckReliable:
				_reliableUnorderedChannel.ProcessAck(packet);
				_packetPool.Recycle(packet);
				break;
			case PacketProperty.AckReliableOrdered:
				_reliableOrderedChannel.ProcessAck(packet);
				_packetPool.Recycle(packet);
				break;
			case PacketProperty.Sequenced:
				_sequencedChannel.ProcessPacket(packet);
				break;
			case PacketProperty.ReliableUnordered:
				_reliableUnorderedChannel.ProcessPacket(packet);
				break;
			case PacketProperty.ReliableOrdered:
				_reliableOrderedChannel.ProcessPacket(packet);
				break;
			case PacketProperty.ReliableSequenced:
				_reliableSequencedChannel.ProcessPacket(packet);
				break;
			case PacketProperty.AckReliableSequenced:
				_reliableSequencedChannel.ProcessAck(packet);
				_packetPool.Recycle(packet);
				break;
			case PacketProperty.Unreliable:
				AddIncomingPacket(packet);
				break;
			case PacketProperty.MtuCheck:
			case PacketProperty.MtuOk:
				ProcessMtuPacket(packet);
				break;
			case PacketProperty.ShutdownOk:
				if (_connectionState == ConnectionState.ShutdownRequested)
				{
					_connectionState = ConnectionState.Disconnected;
				}
				_packetPool.Recycle(packet);
				break;
			default:
				NetDebug.WriteError("Error! Unexpected packet type: " + packet.Property);
				break;
			}
		}

		private void SendMerged()
		{
			if (_mergeCount != 0)
			{
				if (_mergeCount > 1)
				{
					_netManager.SendRaw(_mergeData.RawData, 0, 1 + _mergePos, _remoteEndPoint);
				}
				else
				{
					_netManager.SendRaw(_mergeData.RawData, 3, _mergePos - 2, _remoteEndPoint);
				}
				_mergePos = 0;
				_mergeCount = 0;
			}
		}

		internal void SendUserData(NetPacket packet)
		{
			packet.ConnectionNumber = _connectNum;
			int num = 1 + packet.Size + 2;
			if (num + 20 >= _mtu)
			{
				_netManager.SendRaw(packet, _remoteEndPoint);
				return;
			}
			if (_mergePos + num > _mtu)
			{
				SendMerged();
			}
			FastBitConverter.GetBytes(_mergeData.RawData, _mergePos + 1, (ushort)packet.Size);
			Buffer.BlockCopy(packet.RawData, 0, _mergeData.RawData, _mergePos + 1 + 2, packet.Size);
			_mergePos += packet.Size + 2;
			_mergeCount++;
		}

		public void Flush()
		{
			if (_connectionState != ConnectionState.Connected)
			{
				return;
			}
			lock (_flushLock)
			{
				_reliableOrderedChannel.SendNextPackets();
				_reliableUnorderedChannel.SendNextPackets();
				_reliableSequencedChannel.SendNextPackets();
				_sequencedChannel.SendNextPackets();
				_unreliableChannel.SendNextPackets();
				SendMerged();
			}
		}

		internal void Update(int deltaTime)
		{
			_timeSinceLastPacket += deltaTime;
			switch (_connectionState)
			{
			case ConnectionState.Connected:
				if (_timeSinceLastPacket > _netManager.DisconnectTimeout)
				{
					_netManager.DisconnectPeerForce(this, DisconnectReason.Timeout, SocketError.Success, null);
					return;
				}
				break;
			case ConnectionState.ShutdownRequested:
				if (_timeSinceLastPacket > _netManager.DisconnectTimeout)
				{
					_connectionState = ConnectionState.Disconnected;
					return;
				}
				_shutdownTimer += deltaTime;
				if (_shutdownTimer >= 300)
				{
					_shutdownTimer = 0;
					_netManager.SendRaw(_shutdownPacket, _remoteEndPoint);
				}
				return;
			case ConnectionState.Outcoming:
				_connectTimer += deltaTime;
				if (_connectTimer > _netManager.ReconnectDelay)
				{
					_connectTimer = 0;
					_connectAttempts++;
					if (_connectAttempts > _netManager.MaxConnectAttempts)
					{
						_netManager.DisconnectPeerForce(this, DisconnectReason.ConnectionFailed, SocketError.Success, null);
					}
					else
					{
						_netManager.SendRaw(_connectRequestPacket, _remoteEndPoint);
					}
				}
				return;
			case ConnectionState.Incoming:
			case ConnectionState.Disconnected:
				return;
			}
			_pingSendTimer += deltaTime;
			if (_pingSendTimer >= _netManager.PingInterval)
			{
				_pingSendTimer = 0;
				_pingPacket.Sequence++;
				if (_pingTimer.IsRunning)
				{
					UpdateRoundTripTime((int)_pingTimer.ElapsedMilliseconds);
				}
				_pingTimer.Reset();
				_pingTimer.Start();
				_netManager.SendRaw(_pingPacket, _remoteEndPoint);
			}
			_rttResetTimer += deltaTime;
			if (_rttResetTimer >= _netManager.PingInterval * 3)
			{
				_rttResetTimer = 0;
				_rtt = _avgRtt;
				_rttCount = 1;
			}
			UpdateMtuLogic(deltaTime);
			Flush();
		}

		internal void Recycle(NetPacket packet)
		{
			_packetPool.Recycle(packet);
		}
	}
}
